timetable-app

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

index.tsx (3676B)


      1 import { Box } from '@mui/material';
      2 import { addDays, compareAsc, subDays } from 'date-fns';
      3 
      4 import Head from 'client/components/Head';
      5 import Layout from 'client/components/Layout';
      6 import SlideFooter from 'client/components/SlideFooter';
      7 import DailyView from 'client/features/daily';
      8 import { CLASSES, GRADES } from 'common/constant';
      9 import formatDate from 'common/utils/formatDate';
     10 import { zodClass, zodGrade } from 'common/utils/zod';
     11 import { caller } from 'server/routers/_app';
     12 
     13 import type { DayTimetable } from 'client/features/daily/types';
     14 import type { Class, Grade } from 'common/types';
     15 import type { GetStaticPaths, GetStaticProps, NextPage } from 'next';
     16 
     17 type UrlQuery = {
     18 	class: string;
     19 };
     20 
     21 type PageProps = {
     22 	grade: Grade;
     23 	class: Class;
     24 	calendar: [string, DayTimetable][];
     25 };
     26 
     27 export const getStaticPaths: GetStaticPaths<UrlQuery> = async () => {
     28 	const paths = GRADES.flatMap((g) =>
     29 		CLASSES.map((c) => ({ params: { class: `${g}-${c}` } })),
     30 	);
     31 
     32 	return {
     33 		paths,
     34 		fallback: false,
     35 	};
     36 };
     37 
     38 export const getStaticProps: GetStaticProps<PageProps, UrlQuery> = async ({
     39 	params,
     40 }) => {
     41 	if (typeof params === 'undefined') {
     42 		return {
     43 			notFound: true,
     44 		};
     45 	}
     46 
     47 	// 学年、クラスの数字が問題ないか確認
     48 	const [gradeNumRaw, classNumRaw] = params.class
     49 		.split('-')
     50 		.map((v) => parseInt(v));
     51 	const gradeParsed = zodGrade.safeParse(gradeNumRaw);
     52 	const classParsed = zodClass.safeParse(classNumRaw);
     53 	if (!gradeParsed.success || !classParsed.success) {
     54 		return {
     55 			notFound: true,
     56 		};
     57 	}
     58 	const gradeNumber = gradeParsed.data;
     59 	const classNumber = classParsed.data;
     60 
     61 	const now = new Date();
     62 	const startDate = subDays(now, 5);
     63 	const endDate = addDays(now, 30);
     64 
     65 	// クラスの標準時間割表を取得
     66 	const { standardTimetable } = await caller.classStandard.byGradeAndClass({
     67 		grade: gradeNumber,
     68 		class: classNumber,
     69 	});
     70 
     71 	// 学年の曜日時限時間割表を取得
     72 	const dayPeriodCalendar = await caller.dayPeriodSchedule.byGradeAndPeriod({
     73 		grade: gradeNumber,
     74 		startDate: formatDate(startDate),
     75 		endDate: formatDate(endDate),
     76 	});
     77 
     78 	// クラスの臨時変更を取得
     79 	// 現在、臨時変更は扱っていない
     80 
     81 	// 曜日時限時間割表にクラスの科目データを当てはめていく
     82 	const dayTimetableCalendar = dayPeriodCalendar.map(
     83 		([date, { event, ...sch }]): [string, DayTimetable] => {
     84 			const periods = Object.entries(sch).map(([key, value]) => [
     85 				key,
     86 				{
     87 					// 曜日時限からクラスの科目データを返す
     88 					courses: standardTimetable[value],
     89 					basis: { type: 'dayPeriod', dayPeriod: value },
     90 				},
     91 			]);
     92 			// FIXME: コード汚すぎるので修正する
     93 			return [
     94 				date,
     95 				event
     96 					? {
     97 							date,
     98 							event,
     99 							...Object.fromEntries(periods),
    100 						}
    101 					: {
    102 							date,
    103 							...Object.fromEntries(periods),
    104 						},
    105 			];
    106 		},
    107 	);
    108 
    109 	// 日付順に並び替える
    110 	const sorted = dayTimetableCalendar.toSorted(([, a], [, b]) =>
    111 		compareAsc(a.date, b.date),
    112 	);
    113 
    114 	return {
    115 		props: {
    116 			grade: gradeNumber,
    117 			class: classNumber,
    118 			calendar: sorted,
    119 		},
    120 		revalidate: 60 * 60, // 1時間毎の訪問で再生成
    121 	};
    122 };
    123 
    124 // 5日前~30日後の時間割を表示するページ
    125 const Page: NextPage<PageProps> = (props) => {
    126 	const className = `${props.grade}年${props.class}組`;
    127 	return (
    128 		<>
    129 			<Head title={className} />
    130 			<Layout title={className} footer={<SlideFooter />}>
    131 				<Box width={1}>
    132 					{props.calendar.length ? (
    133 						<DailyView {...props} />
    134 					) : (
    135 						<Box px={2}>
    136 							<p>データがありません</p>
    137 						</Box>
    138 					)}
    139 				</Box>
    140 			</Layout>
    141 		</>
    142 	);
    143 };
    144 
    145 export default Page;